Security News
pnpm 10.0.0 Blocks Lifecycle Scripts by Default
pnpm 10 blocks lifecycle scripts by default to improve security, addressing supply chain attack risks but sparking debate over compatibility and workflow changes.
mobx-utils
Advanced tools
The mobx-utils package provides a set of utility functions and classes to complement MobX, a state management library for JavaScript applications. These utilities help simplify common patterns and enhance the functionality of MobX.
fromPromise
The `fromPromise` utility converts a promise into an observable object, allowing you to easily track the state of asynchronous operations within MobX.
const { fromPromise } = require('mobx-utils');
const { observable } = require('mobx');
const fetchData = () => new Promise(resolve => setTimeout(() => resolve('data'), 1000));
const observablePromise = fromPromise(fetchData());
observablePromise.case({
pending: () => console.log('Loading...'),
fulfilled: value => console.log('Data:', value),
rejected: error => console.log('Error:', error)
});
lazyObservable
The `lazyObservable` utility creates an observable that only starts producing values when it is first observed. This is useful for deferring expensive computations or data fetching until necessary.
const { lazyObservable } = require('mobx-utils');
const lazyData = lazyObservable(sink => {
setTimeout(() => sink('Lazy data'), 1000);
});
lazyData.observe(newValue => console.log('New value:', newValue));
keepAlive
The `keepAlive` utility ensures that a computed value remains active and is not garbage collected, even if there are no observers. This can be useful for maintaining the state of certain computations.
const { keepAlive } = require('mobx-utils');
const { observable, computed } = require('mobx');
const data = observable.box(1);
const computedValue = computed(() => data.get() * 2);
keepAlive(computedValue);
console.log(computedValue.get()); // 2
data.set(2);
console.log(computedValue.get()); // 4
MobX-State-Tree (MST) is a state management library built on top of MobX. It provides a structured way to manage state with a focus on immutability and snapshots. Unlike mobx-utils, which offers utility functions, MST provides a more opinionated and structured approach to state management.
Redux is a popular state management library for JavaScript applications. It uses a single immutable state tree and actions to update the state. While Redux is more verbose and has a steeper learning curve compared to MobX and mobx-utils, it is widely adopted and has a large ecosystem of middleware and tools.
Recoil is a state management library for React applications developed by Facebook. It provides a more fine-grained approach to state management compared to MobX and mobx-utils, with a focus on performance and scalability. Recoil's API is designed to be simple and intuitive for React developers.
Utility functions and common patterns for MobX
This package provides utility functions and common MobX patterns build on top of MobX. It is encouraged to take a peek under the hood and read the sources of these utilities. Feel free to open a PR with your own utilities. For large new features, please open an issue first.
NPM: npm install mobx-utils --save
CDN: https://unpkg.com/mobx-utils/mobx-utils.umd.js
import {function_name} from 'mobx-utils'
fromPromise
takes a Promise and returns an object with 3 observable properties that track
the status of the promise. The returned object has the following observable properties:
value
: either the initial value, the value the Promise resolved to, or the value the Promise was rejected with. use .state
if you need to be able to tell the differencestate
: one of "pending"
, "fulfilled"
or "rejected"
promise
: (not observable) the original promise object
and the following method:case({fulfilled, rejected, pending})
: maps over the result using the provided handlers, or returns undefined
if a handler isn't available for the current promise state.Parameters
promise
IThenable<T> The promise which will be observedinitialValue
T? Optional predefined initial value (optional, default undefined
)modifier
any? MobX modifier, e.g. asFlat
, to be applied to the resolved value (optional, default IDENTITY
)Examples
const fetchResult = fromPromise(fetch("http://someurl"))
// combine with when..
when(
() => fetchResult.state !== "pending"
() => {
console.log("Got ", fetchResult.value)
}
)
// or a mobx-react component..
const myComponent = observer(({ fetchResult }) => {
switch(fetchResult.state) {
case "pending": return <div>Loading...</div>
case "rejected": return <div>Ooops... {fetchResult.value}</div>
case "fulfilled": return <div>Gotcha: {fetchResult.value}</div>
}
})
// or using the case method instead of switch:
const myComponent = observer(({ fetchResult }) =>
fetchResult.case({
pending: () => <div>Loading...</div>
rejected: error => <div>Ooops.. {error}</div>
fulfilled: value => <div>Gotcha: {value}</div>
}))
Note that the status strings are available as constants:
`mobxUtils.PENDING`, `mobxUtils.REJECTED`, `mobxUtil.FULFILLED`
Returns IPromiseBasedObservable<T>
lazyObservable
creates an observable around a fetch
method that will not be invoked
util the observable is needed the first time.
The fetch method receives a sink
callback which can be used to replace the
current value of the lazyObservable. It is allowed to call sink
multiple times
to keep the lazyObservable up to date with some external resource.
Note that it is the current()
call itself which is being tracked by MobX,
so make sure that you don't dereference to early.
Parameters
initialValue
T? optional initialValue that will be returned from current
as long as the sink
has not been called at least once (optional, default undefined
)modifier
any? optional mobx modifier that determines the the comparison and recursion strategy of the observable, for example asFlat
or asStructure
(optional, default IDENTITY
)fetch
Examples
const userProfile = lazyObservable(
sink => fetch("/myprofile").then(profile => sink(profile))
)
// use the userProfile in a React component:
const Profile = observer(({ userProfile }) =>
userProfile.current() === undefined
? <div>Loading user profile...</div>
: <div>{userProfile.current().displayName}</div>
)
// triggers refresh the userProfile
userProfile.refresh()
fromResource
creates an observable which current state can be inspected using .current()
,
and which can be kept in sync with some external datasource that can be subscribed to.
The created observable will only subscribe to the datasource if it is in use somewhere,
(un)subscribing when needed. To enable fromResource
to do that two callbacks need to be provided,
one to subscribe, and one to unsubscribe. The subscribe callback itself will receive a sink
callback, which can be used
to update the current state of the observable, allowing observes to react.
Whatever is passed to sink
will be returned by current()
. The values passed to the sink will not be converted to
observables automatically, but feel free to do so.
It is the current()
call itself which is being tracked,
so make sure that you don't dereference to early.
For inspiration, an example integration with the apollo-client on github,
or the implementation of mobxUtils.now
The following example code creates an observable that connects to a dbUserRecord
,
which comes from an imaginary database and notifies when it has changed.
Parameters
unsubscriber
IDisposer? (optional, default NOOP
)initialValue
T? the data that will be returned by get()
until the sink
has emitted its first data (optional, default undefined
)subscriber
Examples
function createObservableUser(dbUserRecord) {
let currentSubscription;
return fromResource(
(sink) => {
// sink the current state
sink(dbUserRecord.fields)
// subscribe to the record, invoke the sink callback whenever new data arrives
currentSubscription = dbUserRecord.onUpdated(() => {
sink(dbUserRecord.fields)
})
},
() => {
// the user observable is not in use at the moment, unsubscribe (for now)
dbUserRecord.unsubscribe(currentSubscription)
}
)
}
// usage:
const myUserObservable = createObservableUser(myDatabaseConnector.query("name = 'Michel'"))
// use the observable in autorun
autorun(() => {
// printed everytime the database updates its records
console.log(myUserObservable.current().displayName)
})
// ... or a component
const userComponent = observer(({ user }) =>
<div>{user.current().displayName}</div>
)
lib/observable-stream.js:37-51
Converts an expression to an observable stream (a.k.a. TC 39 Observable / RxJS observable). The provided expression is tracked by mobx as long as there are subscribers, automatically emitting when new values become available. The expressions respect transactions.
Parameters
expression
Examples
const user = observable({
firstName: "C.S",
lastName: "Lewis"
})
Rx.Observable
.from(mobxUtils.toStream(() => user.firstname + user.lastName))
.scan(nameChanges => nameChanges + 1, 0)
.subscribe(nameChanges => console.log("Changed name ", nameChanges, "times"))
Returns IObservableStream<T>
lib/observable-stream.js:115-118
Converts an subscribable, observable stream (TC 39 observable / RxJS stream)
into an object which stores the current value (as current
). The subscription can be cancelled through the dispose
method.
Takes an initial value as second optional argument
Parameters
observable
IObservableStream<T>initialValue
Examples
const debouncedClickDelta = MobxUtils.fromStream(Rx.Observable.fromEvent(button, 'click')
.throttleTime(1000)
.map(event => event.clientX)
.scan((count, clientX) => count + clientX, 0)
)
autorun(() => {
console.log("distance moved", debouncedClickDelta.current)
})
lib/create-view-model.js:127-129
createViewModel
takes an object with observable properties (model)
and wraps a viewmodel around it. The viewmodel proxies all enumerable property of the original model with the following behavior:
submit()
is called.The viewmodel exposes the following additional methods, besides all the enumerable properties of the model:
submit()
: copies all the values of the viewmodel to the model and resets the statereset()
: resets the state of the viewmodel, abandoning all local modificationsresetProperty(propName)
: resets the specified property of the viewmodelisDirty
: observable property indicating if the viewModel contains any modificationsisPropertyDirty(propName)
: returns true if the specified property is dirtymodel
: The original model object for which this viewModel was createdYou may use observable arrays, maps and objects with createViewModel
but keep in mind to assign fresh instances of those to the viewmodel's properties, otherwise you would end up modifying the properties of the original model.
Note that if you read a non-dirty property, viewmodel only proxies the read to the model. You therefore need to assign a fresh instance not only the first time you make the assignment but also after calling reset()
or submit()
.
Parameters
model
TExamples
class Todo {
\@observable title = "Test"
}
const model = new Todo()
const viewModel = createViewModel(model);
autorun(() => console.log(viewModel.model.title, ",", viewModel.title))
// prints "Test, Test"
model.title = "Get coffee"
// prints "Get coffee, Get coffee", viewModel just proxies to model
viewModel.title = "Get tea"
// prints "Get coffee, Get tea", viewModel's title is now dirty, and the local value will be printed
viewModel.submit()
// prints "Get tea, Get tea", changes submitted from the viewModel to the model, viewModel is proxying again
viewModel.title = "Get cookie"
// prints "Get tea, Get cookie" // viewModel has diverged again
viewModel.reset()
// prints "Get tea, Get tea", changes of the viewModel have been abandoned
Like normal when
, except that this when
will automatically dispose if the condition isn't met within a certain amount of time.
Parameters
timeout
number? maximum amount when spends waiting before giving up (optional, default 10000
)onTimeout
any? the ontimeout handler will be called if the condition wasn't met within the given time (optional, default ()
)expr
action
Examples
test("expect store to load", t => {
const store = {
items: [],
loaded: false
}
fetchDataForStore((data) => {
store.items = data;
store.loaded = true;
})
whenWithTimeout(
() => store.loaded
() => t.end()
2000,
() => t.fail("store didn't load with 2 secs")
)
})
Returns IDisposer disposer function that can be used to cancel the when prematurely. Neither action or onTimeout will be fired if disposed
MobX normally suspends any computed value that is not in use by any reaction,
and lazily re-evaluates the expression if needed outside a reaction while not in use.
keepAlive
marks a computed value as always in use, meaning that it will always fresh, but never disposed automatically.
Parameters
target
Object an object that has a computed property, created by @computed
or extendObservable
property
string the name of the property to keep alive_1
_2
Examples
const obj = observable({
number: 3,
doubler: function() { return this.number * 2 }
})
const stop = keepAlive(obj, "doubler")
Returns IDisposer stops this keep alive so that the computed value goes back to normal behavior
Parameters
computedValue
IComputedValue<any> created using the computed
function_1
_2
Examples
const number = observable(3)
const doubler = computed(() => number.get() * 2)
const stop = keepAlive(doubler)
// doubler will now stay in sync reactively even when there are no further observers
stop()
// normal behavior, doubler results will be recomputed if not observed but needed, but lazily
Returns IDisposer stops this keep alive so that the computed value goes back to normal behavior
queueProcessor
takes an observable array, observes it and calls processor
once for each item added to the observable array, optionally deboucing the action
Parameters
observableArray
Array<T> observable array instance to trackdebounce
number? optional debounce time in ms. With debounce 0 the processor will run synchronously (optional, default 0
)processor
Examples
const pendingNotifications = observable([])
const stop = queueProcessor(pendingNotifications, msg => {
// show Desktop notification
new Notification(msg);
})
// usage:
pendingNotifications.push("test!")
Returns IDisposer stops the processor
chunkProcessor
takes an observable array, observes it and calls processor
once for a chunk of items added to the observable array, optionally deboucing the action.
The maximum chunk size can be limited by number.
This allows both, splitting larger into smaller chunks or (when debounced) combining smaller
chunks and/or single items into reasonable chunks of work.
Parameters
observableArray
Array<T> observable array instance to trackdebounce
number? optional debounce time in ms. With debounce 0 the processor will run synchronously (optional, default 0
)maxChunkSize
number? optionally do not call on full array but smaller chunks. With 0 it will process the full array. (optional, default 0
)processor
Examples
const trackedActions = observable([])
const stop = chunkProcessor(trackedActions, chunkOfMax10Items => {
sendTrackedActionsToServer(chunkOfMax10Items);
}, 100, 10)
// usage:
trackedActions.push("scrolled")
trackedActions.push("hoveredButton")
// when both pushes happen within 100ms, there will be only one call to server
Returns IDisposer stops the processor
Returns the current date time as epoch number. The date time is read from an observable which is updated automatically after the given interval. So basically it treats time as an observable.
The function takes an interval as parameter, which indicates how often now()
will return a new value.
If no interval is given, it will update each second. If "frame" is specified, it will update each time a
requestAnimationFrame
is available.
Multiple clocks with the same interval will automatically be synchronized.
Countdown example: http://jsbin.com/roqesit/1/edit?js,output
Parameters
interval
(number | "frame"
)? interval in milliseconds about how often the interval should update (optional, default 1000
)Examples
const start = Date.now()
autorun(() => {
console.log("Seconds elapsed: ", (mobxUtils.now() - start) / 1000)
})
FAQs
Utility functions and common patterns for MobX
We found that mobx-utils demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
pnpm 10 blocks lifecycle scripts by default to improve security, addressing supply chain attack risks but sparking debate over compatibility and workflow changes.
Product
Socket now supports uv.lock files to ensure consistent, secure dependency resolution for Python projects and enhance supply chain security.
Research
Security News
Socket researchers have discovered multiple malicious npm packages targeting Solana private keys, abusing Gmail to exfiltrate the data and drain Solana wallets.